package org.example.utils;

import com.itextpdf.barcodes.Barcode128;
import com.itextpdf.barcodes.BarcodeQRCode;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.attach.ITagWorker;
import com.itextpdf.html2pdf.attach.ProcessorContext;
import com.itextpdf.html2pdf.attach.impl.DefaultTagWorkerFactory;
import com.itextpdf.html2pdf.attach.impl.tags.HtmlTagWorker;
import com.itextpdf.html2pdf.attach.impl.tags.ImgTagWorker;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.IPropertyContainer;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.font.FontProvider;
import com.itextpdf.styledxmlparser.node.IElementNode;
import com.itextpdf.styledxmlparser.node.impl.jsoup.node.JsoupElementNode;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.FileCopyUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Objects;

/**
 * @author wangfei
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class PdfUtils {
    private static final ConverterProperties CONVERTER_PROPERTIES;
    private static final PageSize DEFAULT_LABLE_L10 = new PageSize(new Rectangle(0F, 0F, 283.57F, 283.57F));
    private static final PageSize A4 = PageSize.A4;


    public static final String BARCODE_TYPE_BARCODE128 = "barcode128";
    public static final String BARCODE_TYPE_QRBARCODE = "qrBarcode";
    public static final String BARCODE_CODE_NUMBER_VALUE = "code";

    static {
        CONVERTER_PROPERTIES = new ConverterProperties();
        CONVERTER_PROPERTIES.setTagWorkerFactory(new CustomTagWorkerFactory());
        CONVERTER_PROPERTIES.setCharset("UTF-8");
        FontProvider fontProvider = new FontProvider();
        fontProvider.addStandardPdfFonts();

        addFont(fontProvider, "pdf/simsun.ttf");
        addFont(fontProvider, "pdf/simsunb.ttf");
        addFont(fontProvider, "pdf/code128.ttf");
        addFont(fontProvider, "pdf/NotoSansCJKsc-Regular.otf");
        addFont(fontProvider, "pdf/simhei.ttf");
        addFont(fontProvider, "pdf/GenJyuuGothicX-Medium.ttf");
    }

    private static void addFont(FontProvider fontProvider, String fontFileName) {
        try (InputStream resourceAsStream = PdfUtils.class.getClassLoader().getResourceAsStream(fontFileName)) {
            byte[] bytes = FileCopyUtils.copyToByteArray(resourceAsStream);
            fontProvider.addFont(bytes, PdfEncodings.IDENTITY_H);
            CONVERTER_PROPERTIES.setFontProvider(fontProvider);
        } catch (IOException e) {
            log.warn("读取字体文件出错:" + e.getMessage());
        }
    }


    public static ByteArrayInputStream bytes2InputStream(byte[] bytes) {
        return new ByteArrayInputStream(bytes);
    }


    public static byte[] merge(List<InputStream> inputStreams) throws IOException {

        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            PdfDocument newPdf = new PdfDocument(new PdfWriter(out));
            for (InputStream inputStream : inputStreams) {
                Objects.requireNonNull(inputStream);
                PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputStream));
                pdfDocument.copyPagesTo(1, pdfDocument.getNumberOfPages(), newPdf);
            }
            newPdf.close();
            return out.toByteArray();
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            throw new RuntimeException("合并标签失败,系统内部错误. ", ex);
        }

    }

    public static InputStream html2Pdf(String html) throws IOException {
        return html2Pdf(html, CONVERTER_PROPERTIES, DEFAULT_LABLE_L10);
    }

    public static InputStream html2PdfA4(String html) throws IOException {
        return html2Pdf(html, CONVERTER_PROPERTIES, A4);
    }

    public static InputStream html2Pdf(String html, ConverterProperties converterProperties, PageSize pageSize) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PdfWriter pdfWriter = new PdfWriter(out);

        PdfDocument pdfDocument = new PdfDocument(pdfWriter);
        pdfDocument.setDefaultPageSize(pageSize);

        HtmlConverter.convertToPdf(html, pdfDocument, converterProperties);
        return new ByteArrayInputStream(out.toByteArray());
    }


}

class CustomHtmlTagWorker extends HtmlTagWorker {

    public CustomHtmlTagWorker(IElementNode element, ProcessorContext context) {
        super(element, context);
        Document document = (Document) getElementResult();
        document.setLeftMargin(1F);
        document.setRightMargin(1F);
        document.setTopMargin(1F);
        document.setBottomMargin(1F);
    }

    public static CustomHtmlTagWorker newInstance(IElementNode element, ProcessorContext context) {

        return new CustomHtmlTagWorker(element, context);
    }
}

@Slf4j
class CustomTagWorkerFactory extends DefaultTagWorkerFactory {

    @Override
    public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
        if (tag instanceof JsoupElementNode) {
            JsoupElementNode tag1 = (JsoupElementNode) tag;
            String name = tag1.name();
            if ("html".equals(name)) {
                return CustomHtmlTagWorker.newInstance(tag, context);
            }
        }

        if ("img".equals(tag.name())) {

            String codeType = tag.getAttribute("codeType");

            log.info("getAttribute:{}", codeType);


            if (PdfUtils.BARCODE_TYPE_QRBARCODE.equals(codeType)) {
                return new QRCodeTagWorker(tag, context);
            }
            if (PdfUtils.BARCODE_TYPE_BARCODE128.equals(codeType)) {
                return new Barcode128Work(tag, context);
            }

        }

        return super.getCustomTagWorker(tag, context);
    }
}

class Barcode128Work extends ImgTagWorker {
    Image image;

    /**
     * Creates a new {@link ImgTagWorker} instance.
     *
     * @param element the element
     * @param context the context
     */
    public Barcode128Work(IElementNode element, ProcessorContext context) {
        super(element, context);
    }

    @Override
    public void processEnd(IElementNode element, ProcessorContext context) {

        Barcode128 barcode128 = new Barcode128(context.getPdfDocument());

        String code = element.getAttribute(PdfUtils.BARCODE_CODE_NUMBER_VALUE);

        barcode128.setCode(code);

        PdfFormXObject formObject = barcode128.createFormXObject(context.getPdfDocument());

        image = new Image(formObject);
    }

    @Override
    public IPropertyContainer getElementResult() {

        return image;
    }
}

@Slf4j
class QRCodeTagWorker extends ImgTagWorker {
    private BarcodeQRCode qrCode;
    private Image qrCodeAsImage;

    public QRCodeTagWorker(IElementNode element, ProcessorContext context) {
        super(element, context);
    }

    @Override
    public void processEnd(IElementNode element, ProcessorContext context) {

        String code = element.getAttribute(PdfUtils.BARCODE_CODE_NUMBER_VALUE);

        qrCode = new BarcodeQRCode(code);

        //Transform barcode into image
        qrCodeAsImage = new Image(qrCode.createFormXObject(context.getPdfDocument()));

    }

    @Override
    public IPropertyContainer getElementResult() {
        return qrCodeAsImage;
    }
}
